TypeScript · 6711 bytes Raw Blame History
1 'use client';
2
3 import { useState, useEffect, useRef } from 'react';
4 import Link from 'next/link';
5 import { useParams } from 'next/navigation';
6 import { getConflicts, getPeopleByConflict, Conflict, PersonDetail } from '@/lib/api';
7 import Header from '@/components/Header';
8 import DocumentIcon from '@/components/DocumentIcon';
9 import Pagination from '@/components/Pagination';
10
11 export default function ConflictPage() {
12 const params = useParams();
13 const conflictId = parseInt(params.id as string);
14
15 const [conflict, setConflict] = useState<Conflict | null>(null);
16 const [people, setPeople] = useState<PersonDetail[]>([]);
17 const [loading, setLoading] = useState(true);
18 const [error, setError] = useState<string | null>(null);
19 const [currentPage, setCurrentPage] = useState(1);
20 const [itemsPerPage, setItemsPerPage] = useState(30);
21
22 // Ref for scrolling to top of results list
23 const resultsRef = useRef<HTMLDivElement>(null);
24
25 useEffect(() => {
26 async function fetchData() {
27 try {
28 const conflicts = await getConflicts();
29 const currentConflict = conflicts.find(c => c.id === conflictId);
30
31 if (!currentConflict) {
32 throw new Error('Conflict not found');
33 }
34
35 setConflict(currentConflict);
36 const peopleData = await getPeopleByConflict(conflictId);
37 setPeople(peopleData);
38 } catch (err) {
39 setError(err instanceof Error ? err.message : 'Failed to load data');
40 console.error(err);
41 } finally {
42 setLoading(false);
43 }
44 }
45
46 fetchData();
47 }, [conflictId]);
48
49 // Calculate paginated data
50 const totalItems = people.length;
51 const startIndex = (currentPage - 1) * itemsPerPage;
52 const endIndex = startIndex + itemsPerPage;
53 const paginatedPeople = people.slice(startIndex, endIndex);
54
55 // Handlers for pagination
56 const handlePageChange = (page: number) => {
57 setCurrentPage(page);
58 // Scroll to top of results list
59 resultsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
60 };
61
62 const handleItemsPerPageChange = (newItemsPerPage: number) => {
63 setItemsPerPage(newItemsPerPage);
64 setCurrentPage(1); // Reset to first page when changing items per page
65 };
66
67 if (loading) {
68 return (
69 <div className="min-h-screen bg-vmi-cream flex items-center justify-center">
70 <p className="text-gray-600 text-xl">Loading...</p>
71 </div>
72 );
73 }
74
75 if (error || !conflict) {
76 return (
77 <div className="min-h-screen bg-vmi-cream flex items-center justify-center">
78 <div className="text-center">
79 <p className="text-red-600 mb-4 text-xl">{error || 'Conflict not found'}</p>
80 <Link href="/" className="text-vmi-red hover:text-vmi-dark-red underline font-semibold">
81 Return to Home
82 </Link>
83 </div>
84 </div>
85 );
86 }
87
88 return (
89 <div className="min-h-screen bg-vmi-cream">
90 <Header
91 breadcrumbs={[
92 { label: 'Home', href: '/' },
93 { label: conflict.name }
94 ]}
95 />
96
97 {/* Main Content */}
98 <main className="max-w-6xl mx-auto px-4 py-12">
99 {/* Conflict Header */}
100 <div className="bg-vmi-light-gold border-2 border-vmi-gold rounded-lg p-8 mb-12 shadow-xl">
101 <h1 className="text-4xl font-black text-vmi-red mb-4">
102 {conflict.name}
103 </h1>
104 <p className="text-xl text-gray-700 mb-4">
105 {conflict.start_year === conflict.end_year
106 ? conflict.start_year
107 : `${conflict.start_year}${conflict.end_year || 'Present'}`}
108 </p>
109 {conflict.description && (
110 <p className="text-gray-800 leading-relaxed mb-6">{conflict.description}</p>
111 )}
112 <div className="border-t-2 border-vmi-gold pt-6">
113 <p className="text-2xl font-bold text-vmi-red">
114 {conflict.casualty_count} VMI Alumni Gave Their Lives
115 </p>
116 </div>
117 </div>
118
119 {/* People List */}
120 <div ref={resultsRef} className="bg-white border-2 border-gray-300 rounded-lg p-8 shadow-xl">
121 <h2 className="text-3xl font-bold mb-8 text-center text-vmi-red">
122 Honor Roll
123 </h2>
124
125 {people.length === 0 ? (
126 <p className="text-center text-gray-600 text-lg">No casualties recorded yet.</p>
127 ) : (
128 <>
129 {/* Pagination Controls - Top */}
130 <Pagination
131 currentPage={currentPage}
132 totalItems={totalItems}
133 itemsPerPage={itemsPerPage}
134 onPageChange={handlePageChange}
135 onItemsPerPageChange={handleItemsPerPageChange}
136 />
137
138 {/* People Grid */}
139 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
140 {paginatedPeople.map((person) => (
141 <Link
142 key={person.id}
143 href={`/memorial/person/${person.id}`}
144 className="block p-6 border-2 border-gray-200 rounded-lg hover:border-vmi-gold hover:bg-vmi-light-gold transition-all duration-200 group"
145 >
146 <h3 className="text-xl font-bold text-gray-800 group-hover:text-vmi-red transition-colors mb-2 flex items-center gap-2">
147 {person.full_display_name ?
148 person.full_display_name.replace(person.rank + ' ', '').replace(person.rank + ', ', '')
149 : person.display_name.replace(person.rank + ' ', '').replace(person.rank + ', ', '')}
150 {person.pdf_key && <DocumentIcon className="flex-shrink-0" />}
151 </h3>
152 {person.rank && (
153 <p className="text-gray-700 font-semibold">{person.rank}</p>
154 )}
155 {person.unit && (
156 <p className="text-gray-600 text-sm italic">{person.unit}</p>
157 )}
158 {person.death_description && (
159 <p className="text-gray-600 text-sm italic mt-3 line-clamp-3">
160 {person.death_description}
161 </p>
162 )}
163 </Link>
164 ))}
165 </div>
166
167 {/* Pagination Controls - Bottom */}
168 <Pagination
169 currentPage={currentPage}
170 totalItems={totalItems}
171 itemsPerPage={itemsPerPage}
172 onPageChange={handlePageChange}
173 onItemsPerPageChange={handleItemsPerPageChange}
174 />
175 </>
176 )}
177 </div>
178 </main>
179 </div>
180 );
181 }